#!/usr/bin/env python3
# @Date    : 2020-08-05
# @Author  : Bright Li (brt2@qq.com)
# @Link    : https://gitee.com/brt2
# @Version : 0.0.1

import random
import itertools

"""
limit_types = {
    "most_num_everyday": 1,
    "must_in_eachday": "placeholder",
    "must_at_am": True,  # False for 'must_at_pm'
}

requests_types = {
    "prime_time": [0,1,2],  # 占用黄金时间
}
"""


def _permutations_choice(seq, r: int):
    iterable = itertools.permutations(seq, r)
    pool = tuple(iterable)
    return random.choice(pool)


class TimetableSchedule:
    WEEKDAYS_PER_WEEK = 5
    CLASSES_PER_DAY = 7
    PRIME_TIME = [0, 1, 2, 4, 5]

    def __init__(self):
        self.classes = {101, 102, 103, 104, 105}
        self.courses = {
            "语文": {
                "teachers": {
                    "yu_1": [101, 102],
                    "yu_2": [103],
                    "yu_3": [104, 105]
                },
                "num_perweek": 5,
                "limit": {
                    "must_in_eachday": 1,
                },
                "requests": {
                    "prime_time": True
                },
            },
            "数学": {
                "teachers": {
                    "shu_1": [101, 102],
                    "shu_2": [103, 104],
                    "shu_3": [105]
                },
                "num_perweek": 5,
                "limit": {
                    "must_in_eachday": 1,
                },
                "requests": {
                    "prime_time": True
                },
            },
            "英语": {
                "teachers": {
                    "ying_1": [101, 102, 103],
                    "ying_2": [104, 105]
                },
                "num_perweek": 5,
                "limit": {
                    "must_in_eachday": 1,
                },
                "requests": {
                    "prime_time": True
                },
            },
            "政治": {
                "teachers": {
                    "zheng_1": [101, 102, 103, 104, 105]
                },
                "num_perweek": 1,
                "limit": {},
                "requests": {},
            },
            "体育": {
                "teachers": {
                    "ti_1": [101, 102, 103, 104, 105]
                },
                "num_perweek": 2,
                "limit": {},
                "requests": {},
            },
            "音乐": {
                "teachers": {
                    "yue_1": [101, 102, 103, 104, 105]
                },
                "num_perweek": 1,
                "limit": {},
                "requests": {},
            },
            "美术": {
                "teachers": {
                    "mei_1": [101, 102, 103, 104, 105]
                },
                "num_perweek": 1,
                "limit": {},
                "requests": {},
            },
            "自习": {
                "teachers": {
                    " - ": [101, 102, 103, 104, 105]
                },
                "num_perweek": 2,
                "limit": {},
                "requests": {},
            },
            "历史": {
                "teachers": {
                    "li_1": [101, 102],
                    "li_2": [103, 104, 105]
                },
                "num_perweek": 3,
                "limit": {},
                "requests": {},
            },
            "地理": {
                "teachers": {
                    "di_1": [101],
                    "di_2": [102, 103],
                    "di_3": [104, 105]
                },
                "num_perweek": 2,
                "limit": {},
                "requests": {},
            },
            "生物": {
                "teachers": {
                    "sheng_1": [101],
                    "sheng_2": [102, 103],
                    "sheng_3": [104, 105]
                },
                "num_perweek": 3,
                "limit": {},
                "requests": {},
            },
            "物理": {
                "teachers": {
                    "wu_1": [101, 102],
                    "wu_2": [103],
                    "wu_3": [104, 105]
                },
                "num_perweek": 3,
                "limit": {},
                "requests": {},
            },
            "化学": {
                "teachers": {
                    "hua_1": [101, 104, 105],
                    "hua_2": [102, 103],
                },
                "num_perweek": 2,
                "limit": {},
                "requests": {},
            }
        }

    def check_balance(self):
        total_class_num = 0
        for name, dict_data in self.courses.items():
            classroom = []
            for list_classes in dict_data["teachers"].values():
                classroom.extend(list_classes)
            assert set(classroom) == self.classes, f"课程【{name}】在教师分配上错误"

            total_class_num += dict_data["num_perweek"]
        assert total_class_num == self.CLASSES_PER_DAY * self.WEEKDAYS_PER_WEEK, f"课程总数错误【{total_class_num}】"

    def teacher_period_init(self):
        self.teachers_period = {}  # yu_1: {0: [0, 1], 1: [1], 3: [4,5]}
        for dict_info in self.courses.values():
            for teacher in dict_info["teachers"]:
                # if teacher not in self.teachers_period:
                self.teachers_period[teacher] = {}
                # if weekday_idx not in self.teachers_period[teacher]:
                for weekday_idx in range(self.WEEKDAYS_PER_WEEK):
                    self.teachers_period[teacher][weekday_idx] = []

    def make_schedule(self):
        self.check_balance()
        self.teacher_period_init()

        for id_ in self.classes:
            timetable = self.shuffle_seq_for_days(id_)

            print(f"【{id_}】班 202X 第一学期课程表")
            self.show_schedule(timetable)

        from pprint import pprint
        del self.teachers_period[" - "]
        print(">>> Teachers Period")
        pprint(self.teachers_period)

    def shuffle_seq_for_days(self, class_id):
        list_eachday = []
        list_not_eachday = []
        for name, dict_data in self.courses.items():
            for teacher, classes in dict_data["teachers"].items():
                if class_id in classes:
                    class_info = (name, teacher)

                    class_num = dict_data["num_perweek"]
                    if "must_in_eachday" in dict_data["limit"]:
                        list_eachday.append(class_info)
                        class_num -= self.WEEKDAYS_PER_WEEK  # 一般每天都上的只会是一节课

                    if class_num:
                        list_not_eachday.extend([class_info]*class_num)

                    break

        seq_schedule = []
        random.shuffle(list_not_eachday)

        for i in range(self.WEEKDAYS_PER_WEEK):
            self.curr_weekday_idx = i

            _num_per_day = self.CLASSES_PER_DAY - len(list_eachday)
            seq_day = list_not_eachday[_num_per_day*i: _num_per_day*(i+1)] + list_eachday

            seq_day_sorted = self.sort_by_prime(seq_day)
            seq_schedule.extend(seq_day_sorted)

        self.update_teachers_period(seq_schedule)
        return seq_schedule

    def _timetableIndexFormat(self, index):
        weekday_idx = index // self.CLASSES_PER_DAY
        class_idx = index % self.CLASSES_PER_DAY
        return weekday_idx, class_idx

    def update_teachers_period(self, seq_week):
        """ 记录教师课时 """
        for idx, class_info in enumerate(seq_week):
            weekday_idx, class_idx = self._timetableIndexFormat(idx)
            _, teacher = class_info
            self.teachers_period[teacher][weekday_idx].append(class_idx)

    def sort_by_prime(self, seq):  # on one day
        prime_class = []
        others = []
        for class_info in seq:
            class_name, _ = class_info
            if self.courses[class_name]["requests"].get("prime_time"):
                prime_class.append(class_info)
            else:
                others.append(class_info)

        iterable = itertools.permutations(self.PRIME_TIME, len(prime_class))
        prime_pool = tuple(iterable)

        def make_prime():
            rand_prime_idx = random.choice(prime_pool)

            # 检查teacher课时冲突
            _map_prime = dict(zip([t[1] for t in prime_class], rand_prime_idx))
            for _, teacher in prime_class:
                # 当天课程
                list_exists = self.teachers_period[teacher].get(self.curr_weekday_idx)
                if _map_prime[teacher] in list_exists:
                    # raise Exception("教师当前时间已占用")
                    return False
            return dict(zip(rand_prime_idx, prime_class))

        allowance = 100
        while True:
            map_prime = make_prime()
            if map_prime:
                break
            # else:
            allowance -= 1
            if allowance < 0:
                raise Exception("教师当前Prime时间已占用【代码尚未实现对课时组合的遍历】")

        # 验证others的课时冲突
        def make_others():
            random.shuffle(others)
            for idx, class_info in enumerate(others):
                _, teacher = class_info
                if teacher == " - ":
                    continue

                list_exists = self.teachers_period[teacher].get(self.curr_weekday_idx)

                # insert_prime之后的index
                index_true = idx
                for prime_idx in map_prime.keys():
                    if index_true >= prime_idx:
                        index_true += 1

                if index_true in list_exists:
                    # raise Exception("教师当前时间已占用")
                    return False
            return True

        allowance = 100
        while not make_others():
            allowance -= 1
            if allowance < 0:
                raise Exception("教师当前Others时间已占用【代码尚未实现对课时组合的遍历】")

        for idx, class_info in sorted(map_prime.items(), key=lambda d: d[0]):
            others.insert(idx, class_info)

        return others

    # def make_prime_without_conflict(self):


    def show_schedule(self, seq_schedule):
        import numpy as np

        seq_schedule = [f"{name}:{teacher}" for name, teacher in seq_schedule]

        for line in np.asarray(seq_schedule).reshape(self.WEEKDAYS_PER_WEEK, self.CLASSES_PER_DAY).T:
            for item in line:
                print(item, end="\t")
            print()


if __name__ == "__main__":
    table = TimetableSchedule()
    table.make_schedule()